import Pyro.core
import Pyro.naming
from threading import *
import time
import sys
import os
import logging

#A maximum of 2000 concurrent connection
#is allowed for this version of pyOODSM
Pyro.config.PYRO_MAXCONNECTIONS = 2000


#Superclass for all shared objects in the pyOODSM network.
#All classes for shared objects, must inherit from this class.
class DSMclass:

    #Destructor for pyOODSM objects. This should not be necessary, but
    #the function is a safeguard against the situation that the client process
    #is stopped with sys.exit, and the object is still registered on the server.
    #This implements the algorithm described in section 3.4.6.
    def __del__(self):
        if self.__dict__.has_key('ObjectLock'):
            try:
                self.myProxy.unregisterObject(self.refname)
            except:
                pass
        else:
            pass
            
        
    #Implementation of the remote part of the fetch algorithm.
    #The objects __dict__'s is returned to the caller, and the __dict__
    #is reset to contain only the object-lock, the new host, the reference-name
    #and the central server gateway. This is described in section 7.3.1
    #in the report.
    def __migrate__(self, refname):
        logging.debug("Migration of object " + refname + " Requested")
        self.__dict__['objectLock'].acquire()
        self.__dict__['pinLock'].acquire()
        dict_snap={}
        for k in self.__dict__.keys():
            if (not k == 'objectLock') and (not k == 'location'):
                dict_snap[str(k)]=self.__dict__[k]
                del(self.__dict__[k])

        self.__dict__['refname']=refname
        self.__dict__['isLocal'] = dict_snap['isLocal']
        self.__dict__['home'] = dict_snap['home']
        self.__dict__['myProxy'] = dict_snap['myProxy']
        self.__dict__['clientName'] = dict_snap['clientName']
        self.__dict__['pinLock'] = dict_snap['pinLock']
        self.__dict__['fetchLock'] = dict_snap['fetchLock']

        del(dict_snap['isLocal'])
        del(dict_snap['home'])
        del(dict_snap['refname'])
        del(dict_snap['myProxy'])
        del(dict_snap['clientName'])
        del(dict_snap['pinLock'])
        del(dict_snap['fetchLock'])


        self.__dict__['isLocal'] = False
        self.__dict__['pinLock'].release()
        self.__dict__['objectLock'].release()
        logging.debug("Migration of object " + refname + " Finished")
        return dict_snap
    

    
    #Implementation of the pinning method. This function pins the
    #object on the client, until the pinLock is released by the
    #__unpin__ method. This is described in section 7.4 of the report.
    def __pin__(self):
        if self.__dict__.has_key('objectLock'):
            self.objectLock.acquire()
            if self.isLocal:
                self.pinLock.acquire()
                self.objectLock.release()
                logging.debug("pin finish on object: " + self.refname)
            else:
                self.__fetch__()
                self.pinLock.acquire()
                self.objectLock.release() 
        else:
            raise AttributeError("ObjectLock has not yet been initialized")



        
    #Implementation of the __unpin__ method. This simply releases the pinLock
    #of the object. This is described in section 7.4 of the report.
    def __unpin__(self):
        self.pinLock.release()
            




    #Implementation of the fetch algorithm, described in section 7.3.1
    #A new __dict__ i fetched, if possible, from a remote client.
    def __fetch__(self):
        self.__dict__['fetchLock'].acquire()
        logging.info("Client " + self.__dict__['clientName'] + " is fetching object " + self.__dict__['refname'])
        try:
            location = self.__dict__['home'].get_location(self.refname, self.myProxy)
            if not location == self.__dict__['myProxy']:
                dict_snap = location.get(self.refname)
                for k in dict_snap.keys():
                    self.__dict__[k]=dict_snap[k]
                self.__dict__['isLocal'] = True
                logging.info("Client " + self.__dict__['clientName'] + " is fetching object " + self.__dict__['refname'] + " Finished successfully")
                self.__dict__['fetchLock'].release()
                return True
            else:
                self.__dict__['fetchLock'].release()
                return True
        except Exception, e:
            dict_snap = str(e)
            logging.info("Client " + self.__dict__['clientName'] + " is fetching object " + self.__dict__['refname'] + " failed " + dict_snap)
            self.__dict__['fetchLock'].release()
            return False




    #Implementation of the __getattr__ function. The algorithm for this
    #function is described in section 3.4.4 of the report.
    #If the attribute 'name' does not exists in the object and
    #the object is local, an exception is raised.
    #Otherwise the object is fetched and the attribute is returned, if it
    #exists. If the attribute does not exists in the remote object,
    # an exception is raised.
    def __getattr__(self, name):
        logging.debug('getattr on attribute: ' + name)
        self.__dict__['objectLock'].acquire()
        if self.__dict__['isLocal'] == True:
            if self.__dict__.has_key(name):
                value = self.__dict__[name]
                self.__dict__['objectLock'].release()
                return value
            else:
                self.__dict__['objectLock'].release()
                raise AttributeError('Object has no attribute: ' + name)

        else:
            self.__fetch__()
            if self.__dict__.has_key(name):
                value = self.__dict__[name]
                self.__dict__['objectLock'].release()
                return value
            else:
                self.__dict__['objectLock'].release()
                raise AttributeError('Object has no attribute: ' + name)
            
        



    #Implementation of the __delattr__ function. The algorithm for this
    #function is described in section 3.4.10 of the report. If the object exists
    #the attribute is deleted, if it exists in the object,
    # otherwise an exception is raised.
    #If the object is not local, it is fetched, and the attribute is deleted,
    #if it exists. Otherwise an exception is raised.
    def __delattr__(self, name):
        logging.debug('delattr on attribute: ' + name + ' in object: ' + self.refname)
        if self.__dict__.has_key('objectLock'):
            self.__dict__['objectLock'].acquire()
            if (self.__dict__['isLocal'] == True):
                if self.__dict__.has_key(name):
                    del(self.__dict__[name])
                    self.__dict__['objectLock'].release()
                    return True
                else:
                    self.__dict__['objectLock'].release()
                    raise AttributeError(name + 'does not exists')
                
            else:
                self.__fetch__()
                if self.__dict__.has_key(name):
                    del(self.__dict__[name])
                    self.__dict__['objectLock'].release()
                    return True
                else:
                    self.__dict__['objectLock'].release()
                    raise AttributeError(name + 'does not exists')
                
                
        else:
            if self.__dict__.has_key(name):
                del(self.__dict__[name])
                return True
            else:
                raise AttributeError(name + 'does not exists')        
        


            

    #Implementation if the __setattr__ function. The algorithm for this
    #function is described in section 3.4.9. 
    #The attribute is written, but if the object is not local, 
    #it is fetched, and then the attributed is written.
    def __setattr__(self, name, value):
        if self.__dict__.has_key(name):
            self.__dict__[name] = value
            return value
        if self.__dict__.has_key('objectLock'):
            logging.debug('Setattr on attribute: ' + name)
            self.__dict__['objectLock'].acquire()
            if (self.__dict__['isLocal'] == True):
                self.__dict__[name]=value
                self.__dict__['objectLock'].release()
                return value
            else:
                self.__fetch__()
                self.__dict__[name]=value
                self.__dict__['objectLock'].release()
                return value
        else:
            self.__dict__[name] = value
            return value
                




#The DSMmanager class implements the client in the DSM network.
#The client handles registration, deregistration of hosts and
#objects. Furthermore the client can kill the server. This enables
#the programmer to gracefully shutdown the server.
class DSMmanager(Pyro.core.ObjBase):

    #Constructor for setting up connection to the server and setting
    #client-specific attributes, such as the clients name.
    def __init__(self, name, daemon):
        logging.info("Initialization of manager at client " + name + " started")
        self.daemon = daemon
        self.name=name
        self.objects = {}
        self.CentralServer=Pyro.core.getProxyForURI("PYRONAME://DSM_NS")
        self.registerHostLock = Lock()
        self.registerLock = Lock()
        self.unregisterObjectLock = Lock()
        self.unregisterRemoteObjectLock = Lock()
        self.locationLock = Lock()
        self.getLock = Lock()
        if not self.CentralServer:
            print 'Failed to find server...'
            sys.exit()
        logging.info("Manager at client " + name + " initialized")
        return Pyro.core.ObjBase.__init__(self)


    #Destructor for the DSMmanager object. All objects, registered at the
    #manager, is unregistered, and the client is removed from the server.
    def __del__(self):
        logging.info("Client " + self.name + "s destructor called")
        try:
            self.unregisterHost()
        except:
            pass
        self.daemon.shutdown(True)
        logging.info("Client " + self.name + "s destructor executed")


    
    #Function for registration of the host. The algorithm for this is
    #described in section 3.4.3 of the report
    def registerHost(self):
        self.registerHostLock.acquire()
        logging.info("Registration of client " + self.name + " requested") 
        try:
            res = self.CentralServer.registerHost(self.name)
            self.myProxy = Pyro.core.getProxyForURI("PYRONAME://ODB"+self.name)
            logging.info("Registration of client " + self.name + " finished: " + str(self.myProxy))
            self.registerHostLock.release()
            return res
        except Exception, e:
            logging.info("Registration of client " + self.name + " failed")
            self.registerHostLock.release()
            return False



    #Function for registration of an object on the server.
    #The algorithm for this is described in section 7.3.2 of the report
    def register(self, name, object):
        self.registerLock.acquire()
        logging.info("Client " + self.name + " requests registration of object " + name)
        try:
            res=self.CentralServer.register(name, self.name)
        except Exception, e:
            print 'Exception: '+str(e)

        if res == True:
            self.objects[name]=object
            object.__dict__['isLocal'] = True
            object.__dict__['location'] = self.myProxy
            object.__dict__['home'] = self.myProxy
            object.__dict__['refname'] = name
            object.__dict__['myProxy'] = self.myProxy
            object.__dict__['clientName'] = self.name
            object.__dict__['pinLock'] = Lock()
            object.__dict__['fetchLock'] = Lock()
            object.__dict__['objectLock'] = Lock()
        else:
            for k in object.__dict__.keys():
                del(object.__dict__[k])
            object.__dict__['refname']=name
            object.__dict__['isLocal'] = False
            object.__dict__['location'] = []
            object.__dict__['home'] = res
            object.__dict__['myProxy'] = self.myProxy
            object.__dict__['clientName'] = self.name
            object.__dict__['pinLock'] = Lock()
            object.__dict__['fetchLock'] = Lock()
            object.__dict__['objectLock'] = Lock()
            self.objects[name]=object
        logging.info("Client " + self.name + " successfully registrated the object " + name)
        self.registerLock.release()
        return True



    #The unregisterObject function is called by the client-process
    #to unregister an object from the DSM network.
    #The function implements the calling client
    #part of the algorithm described in section 7.3.3.
    def unregisterObject(self, name):
        self.unregisterObjectLock.acquire()
        logging.info("Client " + self.name + " Requested unregistration of object " + name)
        if self.objects.has_key(name):
            obj = self.objects[name]
            if obj.__dict__['isLocal'] == True:
                if (obj.__dict__['home'] == obj.__dict__['myProxy']):
                    try:
                        res =self.CentralServer.unRegisterObject(name, self.name)
                        if not res:
                            del self.objects[name]
                            obj.__dict__['objectLock'].release()
                            logging.info("Client " + self.name + " Failed to unregister object " + name + " - it did not exists in the pyOODSM network")
                            raise NameError ("Object " + name + "Does not not exists in the pyOODSM network")
                        else:
                            del self.objects[name]
                            obj.__dict__['objectLock'].release()
                            self.unregisterObjectLock.release()
                            logging.info("Client " + self.name + " successfully unregistered object " + name)
                            return True
                    except:
                        logging.error("Connection between " + self.name + " and the central server has been lost")
                        raise Exception("Failed to remotely call the central server")
                else:
                    try:
                        obj.__dict__['objectLock'].release()
                        obj.__dict__['home'].revokeObject(name)
                        obj.__dict__['objectLock'].acquire()
                        del self.objects[name]
                        obj.__dict__['objectLock'].release()
                        self.unregisterObjectLock.release()
                        logging.info("Client " + self.name + " successfully unregistered object " + name)
                        return True
                    except:
                        logging.error("Client " + self.name + "could not connect to home node of object " + name)
                        raise Exception("Failed to contact objects home.")
                    
            else:
                if (obj.__dict__['home'] == obj.__dict__['myProxy']):
                    obj.__dict__['objectLock'].acquire()
                    obj.__fetch__(False)
                    try:
                        res =self.CentralServer.unRegisterObject(name, self.name)
                        if not res:
                            del self.objects[name]
                            obj.__dict__['objectLock'].release()
                            logging.info("Client " + self.name + " Failed to unregister object " + name + " - it did not exists in the pyOODSM network")
                            raise NameError ("Object " + name + "Does not not exists in the pyOODSM network")
                        else:
                            del self.objects[name]
                            obj.__dict__['objectLock'].release()
                            self.unregisterObjectLock.release()
                            logging.info("Client " + self.name + " successfully unregistered object " + name)
                            return True
                    except:
                        logging.error("Client " + self.name + " failed to connect with the central server")
                        raise Exception("Failed to remotely call the central server") 
                        

                else:
                    del objects[name]
                    obj.__dict__['objectLock'].release()
                    self.unregisterObjectLock.release()
                    logging.info("Client " + self.name + " successfully unregistered object " + name)
                    return True
        else:
            self.unregisterObjectLock.release()
            return False




    #This function is called remotely from the central server. The function
    #removes, if possible, an object with the name 'name'. There is no return
    #value. The function implements the remote client part of the algorithm
    #described in section 7.3.3 of the report.
    def unRegisterRemoteObject(self, name):
        self.unregisterRemoteObjectLock.acquire()
        logging.debug("Client " + self.name + " got remote unregistration of object " + name)
        if self.objects.has_key(name):
            del self.objects[name]
        logging.debug("Client " + self.name + " removed object from managers list")
        self.unregisterRemoteObjectLock.release()


    def unregisterHost(self):
        logging.info("Client " + self.name + " requests to leave the pyOODSM network")
        self.unregisterHostLock.acquire()
        for obj in self.objects:
            self.unregisterObject(obj.__dict__['refname'])
        try:
            self.CentralServer.unRegisterHost(self.name)
        except:
            self.unregisterHostLock.release()
            logging("Client " + self.name + " failed to contact the central server in unRegisterHost()")
            raise Exception("Failed to contact the Central Server")
        logging.info("Client " + self.name + " successfully unregistered from the pyOODSM network")
        self.unregisterHostLock.release()
            
    

    #This function is called remotely from the server, and returns
    #the __dict__ of the object that the server is trying to fetch.
    #This is described in section 7.3.1 in the report.
    def get(self, name):
        logging.debug("Request for object " + name + " received")
        if not self.objects.has_key(name):
            logging.debug("Object " + name + " did not exists, returning false")
            return False
        else:
            dict_snap = self.objects[name].__migrate__(name)
            logging.debug("Request for object " + name + " finished")
            return dict_snap


    #This function is called remotely from a client, who wishes to
    #get the current location of an object who's home is this client.
    #This is described in section 7.3.1 of the report.
    def get_location(self, name, proxy):
        self.locationLock.acquire()
        logging.debug("Location for object " + name + " requested")
        if not (self.objects.has_key(name)): 
            logging.debug("Object " + name + " did not exists, returning false")
            self.locationLock.release()
            return False
        else:
            location = self.objects[name].location
            self.objects[name].__dict__['location'] = proxy
            logging.debug("Location for object " + name  + " returned" )
            self.locationLock.release()
            return location


    #Called remotely from a client. The function recalls an object who's
    #home is this client. This is a part of the unregisterObject
    #algorithm, described in section 7.3.3 in the report.
    def revokeObject(self, name):
        logging.debug("Client " + self.name + " Revokes object: " + name)
        self.objects[name].__fetch__(False)
        logging.debug("Client " + self.name + " Revoked object: " + name)
        





    #This function is called remotely when the server is killed.
    #Nothing much happens, because there can be no further pyOODSM actions
    #without the server. This is described in section 3.4.8 in the report.
    def serverKilled(self):
        logging.info("Client " + self.name + " received serverKilled - shutting down")
        print "Server killed - can't go on without server"
        self.daemon.shutdown(True)
        os._exit(0)
     


        #This function kills the server. The server kills all clients and
        #then the calling client is killed. This is described in section
        #3.5.8 in the report.
    def killServer(self):
        logging.info("Client " + self.name + " called killServer")
        try:
            self.CentralServer.killServer(self.name)
        except Exception, e:
            pass
        self.daemon.shutdown(True)
        logging.info("Client " + self.name + " shutting down after killServer")
        sys.exit()
        
        





#PYRO daemon for the client. 
def __DSMmanager_t__(daemon):
        daemon.requestLoop()

        


#Function for setting up the manager, PYRO wise.
#The DSMmanager object is created, and registered in the PYRO nameserver.
#thereafter, the manager object is returned and the daemon is started.
def DSMmanager_t(name, logFile, logLevel):
    if logLevel == "debug":
        logLevel = logging.DEBUG
    if logLevel == "info":
        logLevel = logging.INFO
    if logLevel == "error":
        logLevel = logging.ERROR
    if logLevel == "warning":
        logLevel = logging.WARNING
    if logLevel == "critical":
        logLevel = logging.CRITICAL

    logging.basicConfig(level=logLevel, format='[%(asctime)s][%(levelname)s] %(message)s',filename=logFile, filemode='a')
    
        
    daemon=Pyro.core.Daemon()
    manager=DSMmanager(name, daemon)
    locator = Pyro.naming.NameServerLocator()
    ns = locator.getNS()
    daemon.useNameServer(ns)
    print 'Registering name: ODB'+name
    uri=daemon.connect(manager, "ODB"+name)
    logging.info("DSMmanager is starting up.")
    Thread(target=__DSMmanager_t__, args=(daemon,)).start()
    logging.info("DSMmanager is started.")
    return manager
